/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.core.action.save;


import com.inspur.edp.bef.api.be.IBEManager;
import com.inspur.edp.bef.api.parameter.save.SaveParameter;
import com.inspur.edp.bef.core.DebugAdapter;
import com.inspur.edp.bef.core.LcpUtil;
import com.inspur.edp.bef.core.action.base.ActionStack;
import com.inspur.edp.bef.core.action.base.ActionUtil;
import com.inspur.edp.bef.core.action.base.BEMgrActionExecutor;
import com.inspur.edp.bef.core.be.BEManager;
import com.inspur.edp.bef.core.lcp.StandardLcp;
import com.inspur.edp.bef.core.scope.BefScope;
import com.inspur.edp.bef.core.session.BEFuncSessionBase;
import com.inspur.edp.bef.core.session.FuncSession;
import com.inspur.edp.bef.core.session.FuncSessionManager;
import com.inspur.edp.bef.core.tcc.BefTccParam;
import com.inspur.edp.bef.core.tcc.BefTccParamItem;
import com.inspur.edp.bef.core.tcc.TccUtil;
import com.inspur.edp.bef.spi.entity.builtinimpls.BefModelResInfoImpl;
import com.inspur.edp.caf.transaction.api.GlobalTransactionService;
import com.inspur.edp.caf.transaction.api.annoation.tcc.BusinessActionContext;
import com.inspur.edp.cef.api.message.AggregateBizMsgException;
import com.inspur.edp.cef.api.message.BizMessage;
import com.inspur.edp.cef.api.message.IBizMessage;
import com.inspur.edp.cef.api.session.ICefSessionItem;
import com.inspur.edp.cef.entity.exception.CircleDeterminationsFound;
import com.inspur.edp.commonmodel.core.session.distributed.SessionEditToken;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;

//@Component
@Slf4j
public class LcpSaveAction implements ILcpSaveAction {

  @Override
  @Transactional
  public void execute(BusinessActionContext tccCtx, FuncSession session, BEManager beManager, SaveParameter par) {
//		if (!ActionStack.isEmpty())
//		{
//			throw new BefEngineException(ErrorCodes.InvalidSaveOperation, "不支持在自定义动作/实体动作/联动计算/校验规则中调用保存", null);
//		}
    trace(DebugAdapter.Mark_Begin, session.getSessionId(), null);
    boolean isLastNode = false, outmost = false, hasPushStack = false;
    SessionEditToken sessionEditor = FuncSessionManager.getCurrent().beginEdit(session, false);
//    LcpUtil.error("[bef]开始保存"+session != null? session.getSessionId() : null);
    SaveContext ctx = new SaveContext();
    try {
      session.getBESessionItems().stream().forEach(item -> {
        item.getResponseContext().clear();
      });
      isLastNode = ActionStack.isEmpty();
      outmost = ActionStack.isOutmost();
      ActionStack.push(session, "LcpActionSave", beManager);
      hasPushStack = true;
    //Console.writeLine("BELcp Saving: " + BEType + ", SU:" + CafContext.Current.CurrentSU + " Session: " + FuncSessionManager.CurrentFuncSessionId);
      executeCore(ctx, tccCtx, beManager, par, session);
    } catch(Exception e) {
      trace("[Error]funcEnd",null, e);
//      LcpUtil.error("[bef]保存发生异常"+e.getMessage());
      compensate(ctx, e);
      throw e;
    } finally {
      try {
        if (hasPushStack) {
            ActionStack.pop();
        }
        ActionUtil.checkSessionChanged(session.getSessionId(), beManager.getBEType(),
            ActionUtil.CATEGORY_MANAGER_ACTION, "Save");
        if (isLastNode) {
          BEMgrActionExecutor.actionClear(session);
        }
        clearB4SaveDtmFlag(session);
      }finally {
        if(sessionEditor != null)
          FuncSessionManager.getCurrent().endEdit(sessionEditor);
        if (hasPushStack && outmost)
          ActionStack.clear();
      }
    }
    trace(DebugAdapter.Mark_End,null, null);
//    LcpUtil.error("[bef]保存完成"+session != null? session.getSessionId() : null);
  }

  private static void compensate(SaveContext ctx, Exception e) {
    ctx.getCompensaters().forEach(item -> item.execute(e));
  }

  private void executeCore(SaveContext ctx, BusinessActionContext tccCtx, BEManager beManager,
      SaveParameter saveParameter, FuncSession session) {
    GlobalTransactionService tccService = SpringBeanUtils.getBean(GlobalTransactionService.class);
    boolean inTcc = tccService.inTransaction() && tccService.isEnable();
    if (inTcc) {
      Objects.requireNonNull(tccCtx);
      Objects.requireNonNull(tccCtx.getLogId());
      //session.getCallContext().setData("bef_core_tccCtx", tccCtx);
    }

    boolean currentAccepted = false;
    BefScope scope = null;
    try {
      doBeforeSave(ctx, beManager, session, tccCtx);
      scope = new BefScope(session);
      scope.beginScope();
      saveWithScope(session);
      //触发前后事件
      fireSavingEvent(session);
      if (inTcc) {
        checkSupportTcc(session);
      }
      //合并变更 TODO: aif在前事件里用同一个session操作其他be, 会跟着一起保存, 但没走联动和校验
      acceptCurrent(session);
      session.notifySave();
      currentAccepted = true;
      //提交scope
      scope.setComplete();
    } catch (Throwable e) {
      //Console.writeLine("BELcp Saving Exception: " + e.toString() + BEType + ", SU:" + CafContext.Current.CurrentSU + " Session: " + FuncSessionManager.CurrentFuncSessionId);
      if (scope != null)
        scope.setAbort();
      if (currentAccepted)
        session.invalid(e);
      else
        rejectChangesAndLocks(session);
      throw e;
    }

    try {
      if (inTcc) {
        registerTcc(tccCtx, session);
        clearLock4Manual(saveParameter, session);
      } else if(saveParameter.getClearLock()) {
        clearLock(session);
      }
      acceptTransaction(session);
      autoClearBuffer(session);
      clearChangeset(session);
    } catch(Exception e){
      session.invalid(e);
      throw e;
    }
  }

  private static void acceptCurrent(FuncSession session) {
    for (IBEManager beMgr : session.getAllBEManagers())
      ((BEManager) beMgr).mergeCurrentDataToTransaction();
  }

  private static void rejectChangesAndLocks(FuncSession session) {
    for (IBEManager beMgr : session.getAllBEManagers()) {
      ((BEManager) beMgr).clearCurrentChanges();
      session.getFuncSessionItem(beMgr.getBEType()).getLockServiceItem().rejectCurrentLocks();
    }
  }

  private static void autoClearBuffer(FuncSession session) {
    for (IBEManager beMgr : session.getAllBEManagers())
      ((BEManager) beMgr).autoClearBuffer();
  }

  private static void clearChangeset(FuncSession session) {
    for (IBEManager beMgr : session.getAllBEManagers()) {
      //TODO: 此处不应使用manager.clearChangeset(), 那个方法应该其实是rejectTransactional
      ((BEManager) beMgr).getBEManagerContext().getBufferChangeManager().clearChange();
    }
  }

  private static void beforeSaveDeterminate(FuncSession session) {
    final int MaxAdditionalDtmRepeat = 99;
    boolean hasChange;
    int repeatCount = 0;
    Object actionMark = new Object();
    try {
      //循环manager执行保存前联动计算(可能产生新的manager, 后面的manager可能修改前面的manager)
      //循环manager如果有currentTemplateChange的entity执行additional保存前联动计算(可能产生新的manager, 后面的manager可能修改前面的manager)
      do {
        hasChange = false;
        if (MaxAdditionalDtmRepeat < ++repeatCount)
          throw new CircleDeterminationsFound("无");

        List<BEFuncSessionBase> items = new ArrayList<>(session.getSessionItems().size());
        for (ICefSessionItem item : session.getSessionItems().values()) {
          if (!(item instanceof BEFuncSessionBase)
              || ((BEFuncSessionBase) item).getBEManager() == null) {
            continue;
          }
          ((BEFuncSessionBase) item).setActionMark(actionMark);
          items.add((BEFuncSessionBase) item);
        }
        for (BEFuncSessionBase item : items) {
          hasChange |= ((BEManager) item.getBEManager()).beforeSaveDeterminate();
        }
      } while (hasChange);
    } finally {
      for (ICefSessionItem item : session.getSessionItems().values()) {
        if (!(item instanceof BEFuncSessionBase)) {
          continue;
        }
        ((BEFuncSessionBase) item).setActionMark(null);
      }
    }
  }

//  private void rejectChangesAndLocksByReversal(FuncSession session,
//      Map<String, List<IChangeDetail>> reverseTranChg) {
//    for (IBEManager beMgr : session.getAllBEManagers()) {
//      BEFuncSessionBase item = session.getFuncSessionItem(beMgr.getBEType());
//      item.getLockServiceItem().rejectCurrentLocks();
//      List<IChangeDetail> changes = reverseTranChg.get(beMgr.getBEType());
//      if(changes != null)
//        ((BEManager)beMgr).reverse4SaveFailed(changes);
//    }
//  }

  private void registerTcc(BusinessActionContext tccCtx, FuncSession session) {
    BefTccParam befTccParam = new BefTccParam() {{
      setLogId(tccCtx.getLogId());
      setSessionId(session.getSessionId());
    }};
    for (IBEManager item : session.getAllBEManagers()) {
      BEManager beMgr = (BEManager)item;
      BefTccParamItem tccItem = beMgr.buildTccParam();
      if(tccItem != null)
        befTccParam.getItems().add(tccItem);
    }
    if(!befTccParam.getItems().isEmpty()) {
      TccUtil.putTccParam(tccCtx, befTccParam);
      session.addTccParam(befTccParam);
    }
  }

  private void acceptTransaction(FuncSession session) {
    for (IBEManager beMgr : session.getAllBEManagers())
      ((BEManager) beMgr).mergeTransactionDataToOriginal();
  }

  private void doBeforeSave(SaveContext ctx, BEManager currManager, FuncSession session,
      BusinessActionContext tccCtx) {
    BefScope dtmScope = new BefScope(session);
    dtmScope.beginScope();
    try {
      beforeSaveDeterminate(session);
      beforeSaveCodeGenerate(session, ctx);//生成编号(将来执行产生的修改也不再继续触发联动?)
      dtmScope.setComplete();
    } catch (java.lang.Exception e) {
      dtmScope.setAbort();
      throw e;
    }

    BefScope valScope = new BefScope(session);
    valScope.beginScope();
    try {
      beforeSaveValidate(session);//循环一遍Manager执行保存前validation(validation不产生修改只循环一遍)
      valScope.setComplete();
    } catch (java.lang.Exception e) {
      valScope.setAbort();
      throw e;
    }

    List<IBizMessage> msgList = new ArrayList<>();
    boolean result = true;
    for (IBEManager beMgr : session.getAllBEManagers()) {
      if (!((BEManager) beMgr).getResponseContext().hasErrorMessage()) {
        continue;
      }
      result = false;
      if (beMgr != currManager) {
        msgList.addAll(((BEManager) beMgr).getResponseContext().getErrorMessages().stream()
            .map(msg -> new BizMessage() {{
              setMessageParams(msg.getMessageParams());
              setMessageFormat(msg.getMessageFormat());
              setLevel(msg.getLevel());
            }}).collect(Collectors.toList()));
      }
    }
    if (!result) {
      msgList.addAll(0, currManager.getResponseContext().getErrorMessages());
      throw new AggregateBizMsgException(msgList);
    }
  }

  private void checkSupportTcc(FuncSession session) {
    List<IBEManager> beMgrs = session.getAllBEManagers();
    for (IBEManager beMgr : beMgrs)
      ((BEManager) beMgr).checkSupportTcc();
  }

  private void clearB4SaveDtmFlag(FuncSession session) {
    for (IBEManager beMgr : session.getAllBEManagers())
      ((BEManager) beMgr).clearB4SaveDtmFlag();
  }

  private void clearLock(FuncSession session) {
    for (IBEManager beMgr : session.getAllBEManagers())
      beMgr.clearLock();
  }

  private void clearLock4Manual(SaveParameter par, FuncSession session) {
    if (!par.getClearLock())
      return;
    for (IBEManager beMgr : session.getAllBEManagers())
      if(!((BefModelResInfoImpl)beMgr.getModelInfo()).getAutoTccLock())
        beMgr.clearLock();
  }

  private void saveWithScope(FuncSession session) {
    for (IBEManager beMgr : session.getAllBEManagers())
      ((BEManager) beMgr).saveWithScope();
  }

  private void fireSavingEvent(FuncSession session) {
    boolean changedPassAddLockValue = false;
    try {
      if (session.getPassAddLock() == false) {
        session.setPassAddLock(true);
        changedPassAddLockValue = true;
      }
      //TODO: 事件可能回写产生变更, 20180906讨论, 回写的变更集执行aftermodify然后不再发事件
      java.util.List<IBEManager> beMgrs = session.getAllBEManagers();
      for (IBEManager beMgr : beMgrs)
        ((BEManager) beMgr).fireBeforeSave();

      for (IBEManager beMgr : beMgrs)
        ((BEManager) beMgr).fireAfterSave();
    } finally {
      if (changedPassAddLockValue)
        session.setPassAddLock(false);
    }
  }

  private void beforeSaveCodeGenerate(FuncSession session, SaveContext ctx) {
    //TODO: 触发生成编号后联动计算, 联动计算产生的修改可以还需要继续触发联动计算?
    for (IBEManager mgr : session.getAllBEManagers())
      ((BEManager) mgr).beforeSaveCodeGenerate(ctx);
  }

  private void beforeSaveValidate(FuncSession session) {
    for (IBEManager mgr : session.getAllBEManagers())
      ((BEManager) mgr).beforeSaveValidate();
  }

  //region Tcc
  @Transactional
  public void onTccCancel(BusinessActionContext tccContext) throws SQLException {
    BefTccParam tccPar = TccUtil.getTccParam(tccContext);
    if (tccPar == null)
      return;

    String newSession  = FuncSessionManager.getCurrent().initSession().getSessionId();
    try {
      for (BefTccParamItem item : tccPar.getItems()) {
        StandardLcp lcp = (StandardLcp) LcpUtil.getLcp(item.getConfigId());
        lcp.tccCancel(tccContext, item);
      }
    } finally {
      FuncSessionManager.getCurrent().closeSession(newSession);
    }
  }

  @Transactional
  public void onTccConfirm(BusinessActionContext tccContext) throws SQLException {
    BefTccParam tccPar = TccUtil.getTccParam(tccContext);
    if (tccPar == null)
      return;

    String newSessionId = FuncSessionManager.getCurrent().initSession().getSessionId();
    try {
      for (BefTccParamItem item : tccPar.getItems()) {
        StandardLcp lcp = (StandardLcp) LcpUtil.getLcp(item.getConfigId());
        lcp.tccConfirm(tccContext, item);
      }
    } finally {
      FuncSessionManager.getCurrent().closeSession(newSessionId);
    }
  }
  //endregion

  private static void trace(String mark, Object info, Exception e){
    DebugAdapter.trace("LcpSaveAction", mark, e, info);
  }
}
